#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
目录管理器

该模块提供了目录管理的核心功能，包括目录创建、复制、移动、删除、遍历等。

创建时间: 2023-07-12
作者: Python Tools Team
"""

import os
import shutil
from typing import Optional, Union, Dict, Any, List, Callable, Iterator, Tuple, Pattern
from pathlib import Path
import fnmatch
import re

from modules.logger import LogManager
from modules.exception import exception_handler, FileException


class DirectoryManager:
    """
    目录管理器，提供统一的目录操作接口
    
    该类提供了目录创建、复制、移动、删除、遍历等功能。
    """
    
    def __init__(self, logger=None):
        """
        初始化目录管理器
        
        Args:
            logger: 日志记录器，如果为None则使用默认日志记录器
        """
        self.logger = logger or LogManager.get_both_logger(name="directory_manager")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def create(self, dir_path: Union[str, Path], exist_ok: bool = True) -> bool:
        """
        创建目录
        
        Args:
            dir_path: 目录路径
            exist_ok: 如果目录已存在，是否视为成功，默认为True
        
        Returns:
            bool: 创建成功返回True
        
        Raises:
            FileException: 创建失败时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> dir_manager.create('example_dir')
        """
        dir_path = Path(dir_path)
        
        dir_path.mkdir(parents=True, exist_ok=exist_ok)
        
        self.logger.debug(f"已创建目录: {dir_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def delete(self, dir_path: Union[str, Path], recursive: bool = True) -> bool:
        """
        删除目录
        
        Args:
            dir_path: 目录路径
            recursive: 是否递归删除目录内容，默认为True
        
        Returns:
            bool: 删除成功或目录不存在返回True
        
        Raises:
            FileException: 删除失败时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> dir_manager.delete('example_dir')
        """
        dir_path = Path(dir_path)
        
        if not dir_path.exists():
            self.logger.warning(f"要删除的目录不存在: {dir_path}")
            return True
        
        if not dir_path.is_dir():
            raise FileException(f"路径不是目录: {dir_path}")
        
        if recursive:
            shutil.rmtree(dir_path)
        else:
            dir_path.rmdir()
        
        self.logger.debug(f"已删除目录: {dir_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def copy(self, source: Union[str, Path], destination: Union[str, Path], 
             ignore_patterns: Optional[List[str]] = None) -> bool:
        """
        复制目录
        
        Args:
            source: 源目录路径
            destination: 目标目录路径
            ignore_patterns: 要忽略的文件模式列表，如['*.pyc', '*.tmp']
        
        Returns:
            bool: 复制成功返回True
        
        Raises:
            FileException: 源目录不存在或复制失败时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> dir_manager.copy('source_dir', 'destination_dir', ignore_patterns=['*.pyc', '*.tmp'])
        """
        source = Path(source)
        destination = Path(destination)
        
        if not source.exists():
            raise FileException(f"源目录不存在: {source}")
        
        if not source.is_dir():
            raise FileException(f"源路径不是目录: {source}")
        
        # 创建忽略函数
        ignore_func = None
        if ignore_patterns:
            def _ignore_patterns(path, names):
                ignored_names = []
                for pattern in ignore_patterns:
                    ignored_names.extend(fnmatch.filter(names, pattern))
                return set(ignored_names)
            ignore_func = _ignore_patterns
        
        # 如果目标目录存在，先删除
        if destination.exists():
            if destination.is_dir():
                shutil.rmtree(destination)
            else:
                destination.unlink()
        
        shutil.copytree(source, destination, ignore=ignore_func)
        
        self.logger.debug(f"已复制目录: {source} -> {destination}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def move(self, source: Union[str, Path], destination: Union[str, Path]) -> bool:
        """
        移动目录
        
        Args:
            source: 源目录路径
            destination: 目标目录路径
        
        Returns:
            bool: 移动成功返回True
        
        Raises:
            FileException: 源目录不存在或移动失败时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> dir_manager.move('source_dir', 'destination_dir')
        """
        source = Path(source)
        destination = Path(destination)
        
        if not source.exists():
            raise FileException(f"源目录不存在: {source}")
        
        if not source.is_dir():
            raise FileException(f"源路径不是目录: {source}")
        
        # 如果目标目录存在，先删除
        if destination.exists():
            if destination.is_dir():
                shutil.rmtree(destination)
            else:
                destination.unlink()
        
        shutil.move(str(source), str(destination))
        
        self.logger.debug(f"已移动目录: {source} -> {destination}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def list_files(self, dir_path: Union[str, Path], pattern: Optional[str] = None, 
                  recursive: bool = False) -> List[str]:
        """
        列出目录中的文件
        
        Args:
            dir_path: 目录路径
            pattern: 文件名匹配模式，如'*.txt'，默认为None（匹配所有文件）
            recursive: 是否递归遍历子目录，默认为False
        
        Returns:
            List[str]: 文件路径列表
        
        Raises:
            FileException: 目录不存在时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> txt_files = dir_manager.list_files('example_dir', pattern='*.txt')
            >>> for file in txt_files:
            >>>     print(file)
        """
        dir_path = Path(dir_path)
        
        if not dir_path.exists():
            raise FileException(f"目录不存在: {dir_path}")
        
        if not dir_path.is_dir():
            raise FileException(f"路径不是目录: {dir_path}")
        
        result = []
        
        if recursive:
            for root, _, files in os.walk(dir_path):
                for file in files:
                    if pattern is None or fnmatch.fnmatch(file, pattern):
                        result.append(str(Path(root) / file))
        else:
            for item in dir_path.iterdir():
                if item.is_file() and (pattern is None or fnmatch.fnmatch(item.name, pattern)):
                    result.append(str(item))
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def list_dirs(self, dir_path: Union[str, Path], pattern: Optional[str] = None, 
                 recursive: bool = False) -> List[str]:
        """
        列出目录中的子目录
        
        Args:
            dir_path: 目录路径
            pattern: 目录名匹配模式，如'*_data'，默认为None（匹配所有目录）
            recursive: 是否递归遍历子目录，默认为False
        
        Returns:
            List[str]: 子目录路径列表
        
        Raises:
            FileException: 目录不存在时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> subdirs = dir_manager.list_dirs('example_dir')
            >>> for subdir in subdirs:
            >>>     print(subdir)
        """
        dir_path = Path(dir_path)
        
        if not dir_path.exists():
            raise FileException(f"目录不存在: {dir_path}")
        
        if not dir_path.is_dir():
            raise FileException(f"路径不是目录: {dir_path}")
        
        result = []
        
        if recursive:
            for root, dirs, _ in os.walk(dir_path):
                for d in dirs:
                    if pattern is None or fnmatch.fnmatch(d, pattern):
                        result.append(str(Path(root) / d))
        else:
            for item in dir_path.iterdir():
                if item.is_dir() and (pattern is None or fnmatch.fnmatch(item.name, pattern)):
                    result.append(str(item))
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def list_all(self, dir_path: Union[str, Path], file_pattern: Optional[str] = None, 
                dir_pattern: Optional[str] = None, recursive: bool = False) -> Dict[str, List[str]]:
        """
        列出目录中的所有文件和子目录
        
        Args:
            dir_path: 目录路径
            file_pattern: 文件名匹配模式，如'*.txt'，默认为None（匹配所有文件）
            dir_pattern: 目录名匹配模式，如'*_data'，默认为None（匹配所有目录）
            recursive: 是否递归遍历子目录，默认为False
        
        Returns:
            Dict[str, List[str]]: 包含'files'和'dirs'键的字典，分别对应文件和目录的路径列表
        
        Raises:
            FileException: 目录不存在时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> result = dir_manager.list_all('example_dir')
            >>> print(f"文件数量: {len(result['files'])}")
            >>> print(f"目录数量: {len(result['dirs'])}")
        """
        dir_path = Path(dir_path)
        
        if not dir_path.exists():
            raise FileException(f"目录不存在: {dir_path}")
        
        if not dir_path.is_dir():
            raise FileException(f"路径不是目录: {dir_path}")
        
        files = []
        dirs = []
        
        if recursive:
            for root, directories, filenames in os.walk(dir_path):
                root_path = Path(root)
                for d in directories:
                    if dir_pattern is None or fnmatch.fnmatch(d, dir_pattern):
                        dirs.append(str(root_path / d))
                for f in filenames:
                    if file_pattern is None or fnmatch.fnmatch(f, file_pattern):
                        files.append(str(root_path / f))
        else:
            for item in dir_path.iterdir():
                if item.is_dir():
                    if dir_pattern is None or fnmatch.fnmatch(item.name, dir_pattern):
                        dirs.append(str(item))
                elif item.is_file():
                    if file_pattern is None or fnmatch.fnmatch(item.name, file_pattern):
                        files.append(str(item))
        
        return {'files': files, 'dirs': dirs}
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def find_files(self, dir_path: Union[str, Path], name_pattern: Optional[str] = None, 
                  regex_pattern: Optional[Union[str, Pattern]] = None, 
                  min_size: Optional[int] = None, max_size: Optional[int] = None, 
                  recursive: bool = True) -> List[str]:
        """
        查找符合条件的文件
        
        Args:
            dir_path: 目录路径
            name_pattern: 文件名匹配模式（支持通配符），如'*.txt'，默认为None
            regex_pattern: 文件名正则表达式模式，默认为None
            min_size: 最小文件大小（字节），默认为None
            max_size: 最大文件大小（字节），默认为None
            recursive: 是否递归遍历子目录，默认为True
        
        Returns:
            List[str]: 符合条件的文件路径列表
        
        Raises:
            FileException: 目录不存在时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> # 查找所有.txt文件，大小在1KB到1MB之间
            >>> files = dir_manager.find_files('example_dir', name_pattern='*.txt', min_size=1024, max_size=1024*1024)
            >>> for file in files:
            >>>     print(file)
        """
        dir_path = Path(dir_path)
        
        if not dir_path.exists():
            raise FileException(f"目录不存在: {dir_path}")
        
        if not dir_path.is_dir():
            raise FileException(f"路径不是目录: {dir_path}")
        
        # 编译正则表达式
        regex = None
        if regex_pattern is not None:
            if isinstance(regex_pattern, str):
                regex = re.compile(regex_pattern)
            else:
                regex = regex_pattern
        
        result = []
        
        for root, _, files in os.walk(dir_path):
            if not recursive and root != str(dir_path):
                continue
                
            for file in files:
                file_path = Path(root) / file
                
                # 检查文件名模式
                if name_pattern is not None and not fnmatch.fnmatch(file, name_pattern):
                    continue
                
                # 检查正则表达式
                if regex is not None and not regex.search(file):
                    continue
                
                # 检查文件大小
                if min_size is not None or max_size is not None:
                    size = file_path.stat().st_size
                    if min_size is not None and size < min_size:
                        continue
                    if max_size is not None and size > max_size:
                        continue
                
                result.append(str(file_path))
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_dir_size(self, dir_path: Union[str, Path], human_readable: bool = False) -> Union[int, str]:
        """
        获取目录大小
        
        Args:
            dir_path: 目录路径
            human_readable: 是否返回人类可读的大小字符串，默认为False
        
        Returns:
            Union[int, str]: 目录大小（字节数）或人类可读的大小字符串
        
        Raises:
            FileException: 目录不存在时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> size = dir_manager.get_dir_size('example_dir')
            >>> print(f"目录大小: {size} 字节")
            >>> readable_size = dir_manager.get_dir_size('example_dir', human_readable=True)
            >>> print(f"目录大小: {readable_size}")
        """
        dir_path = Path(dir_path)
        
        if not dir_path.exists():
            raise FileException(f"目录不存在: {dir_path}")
        
        if not dir_path.is_dir():
            raise FileException(f"路径不是目录: {dir_path}")
        
        total_size = 0
        for dirpath, _, filenames in os.walk(dir_path):
            for f in filenames:
                fp = Path(dirpath) / f
                if fp.exists():
                    total_size += fp.stat().st_size
        
        if not human_readable:
            return total_size
        
        # 转换为人类可读的大小
        size = total_size
        for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB']:
            if size < 1024 or unit == 'PB':
                break
            size /= 1024
        
        return f"{size:.2f} {unit}"
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def is_empty(self, dir_path: Union[str, Path]) -> bool:
        """
        检查目录是否为空
        
        Args:
            dir_path: 目录路径
        
        Returns:
            bool: 目录为空返回True，否则返回False
        
        Raises:
            FileException: 目录不存在时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> if dir_manager.is_empty('example_dir'):
            >>>     print('目录为空')
            >>> else:
            >>>     print('目录不为空')
        """
        dir_path = Path(dir_path)
        
        if not dir_path.exists():
            raise FileException(f"目录不存在: {dir_path}")
        
        if not dir_path.is_dir():
            raise FileException(f"路径不是目录: {dir_path}")
        
        # 检查目录是否有任何内容
        return not any(dir_path.iterdir())
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def walk(self, dir_path: Union[str, Path], topdown: bool = True) -> Iterator[Tuple[str, List[str], List[str]]]:
        """
        遍历目录树
        
        Args:
            dir_path: 目录路径
            topdown: 是否自顶向下遍历，默认为True
        
        Returns:
            Iterator[Tuple[str, List[str], List[str]]]: 生成器，每次返回(当前目录路径, 子目录列表, 文件列表)
        
        Raises:
            FileException: 目录不存在时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> for root, dirs, files in dir_manager.walk('example_dir'):
            >>>     print(f"当前目录: {root}")
            >>>     print(f"子目录: {dirs}")
            >>>     print(f"文件: {files}")
        """
        dir_path = Path(dir_path)
        
        if not dir_path.exists():
            raise FileException(f"目录不存在: {dir_path}")
        
        if not dir_path.is_dir():
            raise FileException(f"路径不是目录: {dir_path}")
        
        return os.walk(dir_path, topdown=topdown)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def clean(self, dir_path: Union[str, Path], keep_dir: bool = True) -> bool:
        """
        清空目录内容
        
        Args:
            dir_path: 目录路径
            keep_dir: 是否保留目录本身，默认为True
        
        Returns:
            bool: 清空成功返回True
        
        Raises:
            FileException: 目录不存在或清空失败时抛出
        
        Examples:
            >>> dir_manager = DirectoryManager()
            >>> dir_manager.clean('example_dir')  # 清空目录但保留目录本身
            >>> dir_manager.clean('example_dir', keep_dir=False)  # 删除目录及其内容
        """
        dir_path = Path(dir_path)
        
        if not dir_path.exists():
            if keep_dir:
                raise FileException(f"目录不存在: {dir_path}")
            return True
        
        if not dir_path.is_dir():
            raise FileException(f"路径不是目录: {dir_path}")
        
        # 删除目录内容
        for item in dir_path.iterdir():
            if item.is_dir():
                shutil.rmtree(item)
            else:
                item.unlink()
        
        # 如果不保留目录本身，则删除目录
        if not keep_dir:
            dir_path.rmdir()
            self.logger.debug(f"已删除目录: {dir_path}")
        else:
            self.logger.debug(f"已清空目录内容: {dir_path}")
        
        return True
    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        # 没有需要清理的资源
        pass